using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;

namespace LumiSoft.Net
{
    /// <summary>
    /// This class implements extended socket, provides usefull methods for reading and writing data to socket.
    /// </summary>
    public class SocketEx : IDisposable
    {
        private delegate void BufferDataBlockCompleted(Exception x,object tag);

        private Socket        m_pSocket           = null;
        private NetworkStream m_pSocketStream     = null;
        private SslStream     m_pSslStream        = null;
        private bool          m_SSL               = false;
        private byte[]        m_Buffer            = null;
        private int           m_OffsetInBuffer    = 0;
        private int           m_AvailableInBuffer = 0;
        private Encoding      m_pEncoding         = null;
        private SocketLogger  m_pLogger           = null;
        private string        m_Host              = "";
        private DateTime      m_LastActivityDate;
        private long          m_ReadedCount       = 0;
        private long          m_WrittenCount      = 0;

        /// <summary>
        /// Default constructor.
        /// </summary>
        public SocketEx()
        {
            m_Buffer = new byte[8000];
            m_pEncoding = Encoding.UTF8;
            m_LastActivityDate = DateTime.Now;
            
            m_pSocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
            m_pSocket.ReceiveTimeout = 60000;
            m_pSocket.SendTimeout = 60000;
        }

        /// <summary>
        /// Socket wrapper. NOTE: You must pass connected socket here !
        /// </summary>
        /// <param name="socket">Socket.</param>
        public SocketEx(Socket socket)
        {
            m_Buffer = new byte[8000];
            m_pEncoding = Encoding.UTF8;
            m_LastActivityDate = DateTime.Now;
            
            m_pSocket = socket;

            if(socket.ProtocolType == ProtocolType.Tcp){
                m_pSocketStream = new NetworkStream(socket,false);
            }
            m_pSocket.ReceiveTimeout = 60000;
            m_pSocket.SendTimeout = 60000;
        }

        #region method Dispose

        /// <summary>
        /// Clean up any resouces being used.
        /// </summary>
        public void Dispose()
        {
            Disconnect();
        }

        #endregion


        #region method Connect

        /// <summary>
        /// Connects to the specified host.
        /// </summary>
        /// <param name="endpoint">IP endpoint where to connect.</param>
        public void Connect(IPEndPoint endpoint)
        {
            Connect(endpoint.Address.ToString(),endpoint.Port,false);
        }

        /// <summary>
        /// Connects to the specified host.
        /// </summary>
        /// <param name="endpoint">IP endpoint where to connect.</param>
        /// <param name="ssl">Specifies if to connected via SSL.</param>
        public void Connect(IPEndPoint endpoint,bool ssl)
        {
            Connect(endpoint.Address.ToString(),endpoint.Port,ssl);
        }

        /// <summary>
        /// Connects to the specified host.
        /// </summary>
        /// <param name="host">Host name or IP where to connect.</param>
        /// <param name="port">TCP port number where to connect.</param>
        public void Connect(string host,int port)
        {
            Connect(host,port,false);
        }

        /// <summary>
        /// Connects to the specified host.
        /// </summary>
        /// <param name="host">Host name or IP where to connect.</param>
        /// <param name="port">TCP port number where to connect.</param>
        /// <param name="ssl">Specifies if to connected via SSL.</param>
        public void Connect(string host,int port,bool ssl)
        {    
            m_pSocket.Connect(new IPEndPoint(System.Net.Dns.Resolve(host).AddressList[0],port));
            // mono won't support it
            //m_pSocket.Connect(host,port);

            m_Host = host;
            m_pSocketStream = new NetworkStream(m_pSocket,false);

            if(ssl){
                SwitchToSSL_AsClient();
            }
        }

        #endregion

        #region method Disconnect

        /// <summary>
        /// Disconnects socket.
        /// </summary>
        public void Disconnect()
        {    
            lock(this){
                if(m_pSocket != null){
                    m_pSocket.Close();
                }

                m_SSL = false;
                m_pSocketStream = null;
                m_pSslStream = null;
                m_pSocket = null;
                m_OffsetInBuffer = 0;
                m_AvailableInBuffer = 0;
                m_Host = "";
                m_ReadedCount = 0;
                m_WrittenCount = 0;
            }
        }

        #endregion

        #region method Shutdown

		/// <summary>
		/// Shutdowns socket.
		/// </summary>
		/// <param name="how"></param>
		public void Shutdown(SocketShutdown how)
		{
			m_pSocket.Shutdown(how);
		}

		#endregion

        #region method Bind

        /// <summary>
        /// Associates a Socket with a local endpoint.
        /// </summary>
        /// <param name="loaclEP"></param>
        public void Bind(EndPoint loaclEP)
        {
            m_pSocket.Bind(loaclEP);
        }

        #endregion

        #region method Listen

        /// <summary>
        /// Places a Socket in a listening state.
        /// </summary>
        /// <param name="backlog">The maximum length of the pending connections queue. </param>
        public void Listen(int backlog)
        {
            m_pSocket.Listen(backlog);
        }

        #endregion

        /// <summary>
        /// TODO:
        /// </summary>
        /// <param name="ssl"></param>
        /// <returns></returns>
        public SocketEx Accept(bool ssl)
        {
            Socket s = m_pSocket.Accept();
            return new SocketEx(s);
        }

        #region method SwitchToSSL

        /// <summary>
        /// Switches socket to SSL mode. Throws excpetion is socket is already in SSL mode.
        /// </summary>
        /// <param name="certificate">Certificate to use for SSL.</param>
        public void SwitchToSSL(X509Certificate certificate)
        {
            if(m_SSL){
                throw new Exception("Error can't switch to SSL, socket is already in SSL mode !");
            }

            SslStream sslStream = new SslStream(m_pSocketStream);
            sslStream.AuthenticateAsServer(certificate);

            m_SSL = true;
            m_pSslStream = sslStream;
        }

        #endregion

        #region method SwitchToSSL_AsClient

        /// <summary>
        /// Switches socket to SSL mode. Throws excpetion is socket is already in SSL mode.
        /// </summary>
        public void SwitchToSSL_AsClient()
        {
            if(m_SSL){
                throw new Exception("Error can't switch to SSL, socket is already in SSL mode !");
            }

            SslStream sslStream = new SslStream(m_pSocketStream,true,this.RemoteCertificateValidationCallback);
            sslStream.AuthenticateAsClient(m_Host);

            m_SSL = true;
            m_pSslStream = sslStream;
        }

        private bool RemoteCertificateValidationCallback(Object sender,X509Certificate certificate,X509Chain chain,SslPolicyErrors sslPolicyErrors)
        {
            if(sslPolicyErrors == SslPolicyErrors.None || sslPolicyErrors == SslPolicyErrors.RemoteCertificateNameMismatch){
                return true;
            }


            // Do not allow this client to communicate with unauthenticated servers.
            return false;
        }

        #endregion



        #region method ReadByte

        /// <summary>
        /// Reads byte from socket. Returns readed byte or -1 if socket is shutdown and tehre is no more data available.
        /// </summary>
        /// <returns>Returns readed byte or -1 if socket is shutdown and tehre is no more data available.</returns>
        public int ReadByte()
        {              
            BufferDataBlock();            
            // Socket is shutdown
            if(m_AvailableInBuffer == 0){
                m_OffsetInBuffer = 0;
                m_AvailableInBuffer = 0;
                return -1;
            }

            m_OffsetInBuffer++; 
            m_AvailableInBuffer--;

            return m_Buffer[m_OffsetInBuffer - 1];
        }

        #endregion

        #region method ReadLine

        /// <summary>
        /// Reads line from socket. Maximum line length is 4000 bytes. NOTE: CRLF isn't written to destination stream.
        /// If maximum allowed line length is exceeded line is read to end, but isn't stored to buffer and exception
        /// is thrown after line reading.
        /// </summary>
        /// <returns>Returns readed line.</returns>
        public string ReadLine()
        {
            return ReadLine(4000);
        }

        /// <summary>
        /// Reads line from socket.NOTE: CRLF isn't written to destination stream.
        /// If maximum allowed line length is exceeded line is read to end, but isn't stored to buffer and exception
        /// is thrown after line reading.
        /// </summary>
        /// <param name="maxLineLength">Maximum line length in bytes.</param>
        /// <returns>Returns readed line.</returns>
        public string ReadLine(int maxLineLength)
        {
            return m_pEncoding.GetString(ReadLineByte(maxLineLength));
        }

        /// <summary>
        /// Reads line from socket.NOTE: CRLF isn't written to destination stream.
        /// If maximum allowed line length is exceeded line is read to end, but isn't stored to buffer and exception
        /// is thrown after line reading.
        /// </summary>
        /// <param name="maxLineLength">Maximum line length in bytes.</param>
        /// <returns>Returns readed line.</returns>
        public byte[] ReadLineByte(int maxLineLength)
        {                        
            MemoryStream strmLineBuf = new MemoryStream();
			ReadLine(strmLineBuf,maxLineLength);

            return strmLineBuf.ToArray();
        }

        /// <summary>
        /// Reads line from socket and stores it to specified stream. NOTE: CRLF isn't written to destination stream.
        /// If maximum allowed line length is exceeded line is read to end, but isn't stored to buffer and exception
        /// is thrown after line reading.
        /// </summary>
        /// <param name="stream">Stream where to store readed line.</param>
        /// <param name="maxLineLength">Maximum line length in bytes.</param>
        public void ReadLine(Stream stream,int maxLineLength)
        {
            // Delay last byte writing, this is because CR, if next is LF, then skip CRLF and terminate reading.

            int lastByte    = ReadByte();
            int currentByte = ReadByte();
            int readedCount = 2;
            while(currentByte > -1){
                // We got line
                if(lastByte == (byte)'\r' && currentByte == (byte)'\n'){
                    // Logging stuff
				    if(m_pLogger != null){
					    if(stream.CanSeek && stream.Length < 200){
                            byte[] readedData = new byte[stream.Length];
                            stream.Position = 0;
                            stream.Read(readedData,0,readedData.Length);
						    m_pLogger.AddReadEntry(m_pEncoding.GetString(readedData),readedCount);
					    }
					    else{
						    m_pLogger.AddReadEntry("Big binary line, readed " + readedCount.ToString() + " bytes.",readedCount);
					    }
                    }

                    stream.Flush();

                    // Maximum allowed length exceeded
                    if(readedCount > maxLineLength){                
                        throw new ReadException(ReadReplyCode.LengthExceeded,"Maximum allowed line length exceeded !");
                    }

                    return;
                }
                else{
                    // Maximum allowed length exceeded, just don't store data.
                    if(readedCount < maxLineLength){
                        stream.WriteByte((byte)lastByte);
                    }
                    lastByte = currentByte;
                }

                // Read next byte
                currentByte = ReadByte();
                readedCount++;
            }

            // We should not reach there, if so then socket closed
            // Logging stuff
		    if(m_pLogger != null){
                m_pLogger.AddTextEntry("Remote host closed socket !");
            }
            throw new ReadException(ReadReplyCode.SocketClosed,"Connected host closed socket, read line terminated unexpectedly !");
        }

        #endregion

        #region method ReadSpecifiedLength

        /// <summary>
        /// Reads specified length of data from socket and store to specified stream.
        /// </summary>
        /// <param name="lengthToRead">Specifies how much data to read from socket.</param>
        /// <param name="storeStream">Stream where to store data.</param>
        public void ReadSpecifiedLength(int lengthToRead,Stream storeStream)
        {
            while(lengthToRead > 0){
                BufferDataBlock();
                // Socket is shutdown
                if(m_AvailableInBuffer == 0){
                    m_OffsetInBuffer = 0;
                    m_AvailableInBuffer = 0;
                    // Logging stuff
                    if(m_pLogger != null){
                        m_pLogger.AddTextEntry("Remote host closed socket, all data wans't readed !");
                    }
                    throw new Exception("Remote host closed socket, all data wans't readed !");
                }

                // We have all data in buffer what we need.
                if(m_AvailableInBuffer >= lengthToRead){
                    storeStream.Write(m_Buffer,m_OffsetInBuffer,lengthToRead);
                    storeStream.Flush();
                                        
                    m_OffsetInBuffer += lengthToRead;
                    m_AvailableInBuffer -= lengthToRead;
                    lengthToRead = 0;

                    // Logging stuff
				    if(m_pLogger != null){
					    if(storeStream.CanSeek && storeStream.Length < 200){
                            byte[] readedData = new byte[storeStream.Length];
                            storeStream.Position = 0;
                            storeStream.Read(readedData,0,readedData.Length);
						    m_pLogger.AddReadEntry(m_pEncoding.GetString(readedData),lengthToRead);
					    }
					    else{
						    m_pLogger.AddReadEntry("Big binary data, readed " + lengthToRead.ToString() + " bytes.",lengthToRead);
					    }
                    }                    
                }
                // We need more data than buffer has,read all buffer data.
                else{
                    storeStream.Write(m_Buffer,m_OffsetInBuffer,m_AvailableInBuffer);
                    storeStream.Flush();

                    lengthToRead -= m_AvailableInBuffer;
                    m_OffsetInBuffer = 0;
                    m_AvailableInBuffer = 0;
                }
            }
        }

        #endregion

        #region method ReadPeriodTerminated

        /// <summary>
        /// Reads period terminated string. The data is terminated by a line containing only a period, that is,
        /// the character sequence "&lt;CRLF&gt;.&lt;CRLF&gt;".
        /// When a line of text is received, it checks the line. If the line is composed of a single period,
        /// it is treated as the end of data indicator.  If the first character is a period and there are 
        /// other characters on the line, the first character is deleted.
        /// If maximum allowed data length is exceeded data is read to end, but isn't stored to buffer and exception
        /// is thrown after data reading.
        /// </summary>
        /// <param name="maxLength">Maximum data length in bytes.</param>
        /// <returns></returns>
        public string ReadPeriodTerminated(int maxLength)
        {
            MemoryStream ms = new MemoryStream();
            ReadPeriodTerminated(ms,maxLength);

            return m_pEncoding.GetString(ms.ToArray());
        }

        /// <summary>
        /// Reads period terminated data. The data is terminated by a line containing only a period, that is,
        /// the character sequence "&lt;CRLF&gt;.&lt;CRLF&gt;".
        /// When a line of text is received, it checks the line. If the line is composed of a single period,
        /// it is treated as the end of data indicator.  If the first character is a period and there are 
        /// other characters on the line, the first character is deleted.
        /// If maximum allowed data length is exceeded data is read to end, but isn't stored to stream and exception
        /// is thrown after data reading.
        /// </summary>
        /// <param name="stream">Stream where to store readed data.</param>
        /// <param name="maxLength">Maximum data length in bytes.</param>
        public void ReadPeriodTerminated(Stream stream,int maxLength)
        {            
            /* When a line of text is received by the server, it checks the line.
               If the line is composed of a single period, it is treated as the 
               end of data indicator.  If the first character is a period and 
               there are other characters on the line, the first character is deleted.
            */

            // Delay last byte writing, this is because CR, if next is LF, then last byte isn't written.
            
            byte[] buffer           = new byte[8000];
            int    positionInBuffer = 0;

            int lastByte    = ReadByte();
            int currentByte = ReadByte();
            int readedCount = 2;
            bool lineBreak  = false;
            bool expectCRLF = false;
            while(currentByte > -1){                                
                // We got <CRLF> + 1 char, we must skip that char if it is '.'.
                if(lineBreak){
                    lineBreak = false;

                    // We must skip that char if it is '.'
                    if(currentByte == '.'){
                        expectCRLF = true;

                        currentByte = ReadByte();
                    }                    
                }
                // We got <CRLF>
                else if(lastByte == (byte)'\r' && currentByte == (byte)'\n'){
                    lineBreak = true;

                    // We have <CRLF>.<CRLF>, skip last <CRLF>.
                    if(expectCRLF){
                        // There is data in buffer, flush it
                        if(positionInBuffer > 0){
                            stream.Write(buffer,0,positionInBuffer);
                            positionInBuffer = 0;
                        }

                        // Logging stuff
				        if(m_pLogger != null){
					        if(stream.CanSeek && stream.Length < 200){
                                byte[] readedData = new byte[stream.Length];
                                stream.Position = 0;
                                stream.Read(readedData,0,readedData.Length);
						        m_pLogger.AddReadEntry(m_pEncoding.GetString(readedData),readedCount);
					        }
					        else{
						        m_pLogger.AddReadEntry("Big binary data, readed " + readedCount.ToString() + " bytes.",readedCount);
					        }
                        }

                        // Maximum allowed length exceeded
                        if(readedCount > maxLength){                
                            throw new ReadException(ReadReplyCode.LengthExceeded,"Maximum allowed line length exceeded !");
                        }

                        return;
                    }
                }

                // current char isn't CRLF part, so it isn't <CRLF>.<CRLF> terminator.
                if(expectCRLF && !(currentByte == (byte)'\r' || currentByte == (byte)'\n')){
                    expectCRLF = false;
                }

                // Maximum allowed length exceeded, just don't store data.
                if(readedCount < maxLength){
                    // Buffer is filled up, write buffer to stream
                    if(positionInBuffer > (buffer.Length - 2)){
                        stream.Write(buffer,0,positionInBuffer);
                        positionInBuffer = 0;
                    }

                    buffer[positionInBuffer] = (byte)lastByte;
                    positionInBuffer++;
                }
                                
                // Read next byte
                lastByte = currentByte;
                currentByte = ReadByte();
                readedCount++;
            }

            // We never should reach there, only if data isn't <CRLF>.<CRLF> terminated.
            // Logging stuff
            if(m_pLogger != null){
                m_pLogger.AddTextEntry("Remote host closed socket and data wasn't <CRLF>.<CRLF> terminated !");
            }
            throw new Exception("Remote host closed socket and data wasn't <CRLF>.<CRLF> terminated !");            
        }

        #endregion


        #region method Write

        /// <summary>
        /// Writes specified data to socket.
        /// </summary>
        /// <param name="data">Data to write to socket.</param>
        public void Write(string data)
        {
            Write(new MemoryStream(m_pEncoding.GetBytes(data)));
        }

        /// <summary>
        /// Writes specified data to socket.
        /// </summary>
        /// <param name="data">Data to to wite to socket.</param>
        public void Write(byte[] data)
        {
            Write(new MemoryStream(data));
        }

        /// <summary>
        /// Writes specified data to socket.
        /// </summary>
        /// <param name="data">Data to to wite to socket.</param>
        /// <param name="offset">Offset in data from where to start sending data.</param>
        /// <param name="length">Lengh of data to send.</param>
        public void Write(byte[] data,int offset,int length)
        {
            MemoryStream ms = new MemoryStream(data);
            ms.Position = offset;
            Write(ms,length);
        }

        /// <summary>
        /// Writes specified data to socket.
        /// </summary>
        /// <param name="stream">Stream which data to write to socket. Reading starts from stream current position and will be readed to EOS.</param>
        public void Write(Stream stream)
        {
            m_pSocket.NoDelay = false;

            byte[] buffer = new byte[4000];
            int sentCount = 0;
            int readedCount = stream.Read(buffer,0,buffer.Length);
            while(readedCount > 0){
                if(m_SSL){
                    m_pSslStream.Write(buffer,0,readedCount);
                    m_pSslStream.Flush();
                }
                else{
                    m_pSocketStream.Write(buffer,0,readedCount);
                }

                sentCount += readedCount;
                m_WrittenCount += readedCount;                
                m_LastActivityDate = DateTime.Now;

                readedCount = stream.Read(buffer,0,buffer.Length);
            }            

            // Logging stuff
		    if(m_pLogger != null){
			    if(sentCount < 200){
					m_pLogger.AddSendEntry(m_pEncoding.GetString(buffer,0,sentCount),sentCount);
		        }
			    else{
			        m_pLogger.AddSendEntry("Big binary data, sent " + sentCount.ToString() + " bytes.",sentCount);
		        }
            }
        }

        /// <summary>
        /// Writes specified data to socket.
        /// </summary>
        /// <param name="stream">Stream which data to write to socket. Reading starts from stream current position and specified count will be readed.</param>
        /// <param name="count">Number of bytes to read from stream and write to socket.</param>
        public void Write(Stream stream,long count)
        {            
            m_pSocket.NoDelay = false;

            byte[] buffer = new byte[4000];
            int sentCount = 0;
            int readedCount = 0;
            if((count - sentCount) > buffer.Length){
                readedCount = stream.Read(buffer,0,buffer.Length);
            }
            else{
                readedCount = stream.Read(buffer,0,(int)(count - sentCount));
            }
            while(sentCount < count){
                if(m_SSL){
                    m_pSslStream.Write(buffer,0,readedCount);
                    m_pSslStream.Flush();
                }
                else{
                    m_pSocketStream.Write(buffer,0,readedCount);
                }

                sentCount += readedCount;
                m_WrittenCount += readedCount;                
                m_LastActivityDate = DateTime.Now;

                if((count - sentCount) > buffer.Length){
                    readedCount = stream.Read(buffer,0,buffer.Length);
                }
                else{
                    readedCount = stream.Read(buffer,0,(int)(count - sentCount));
                }
            }            

            // Logging stuff
		    if(m_pLogger != null){
			    if(sentCount < 200){
					m_pLogger.AddSendEntry(m_pEncoding.GetString(buffer,0,sentCount),sentCount);
		        }
			    else{
			        m_pLogger.AddSendEntry("Big binary data, sent " + sentCount.ToString() + " bytes.",sentCount);
		        }
            }
        }

        #endregion

        #region method WriteLine

        /// <summary>
        /// Writes specified line to socket. If line isn't CRLF terminated, CRLF is added automatically.
        /// </summary>
        /// <param name="line">Line to write to socket.</param>
        public void WriteLine(string line)
        {
            WriteLine(m_pEncoding.GetBytes(line));
        }

        /// <summary>
        /// Writes specified line to socket. If line isn't CRLF terminated, CRLF is added automatically.
        /// </summary>
        /// <param name="line">Line to write to socket.</param>
        public void WriteLine(byte[] line)
        {
            // Don't allow to wait after we send data, because there won't no more data
            m_pSocket.NoDelay = true;

            // <CRF> is missing, add it
            if(line.Length < 2 || (line[line.Length - 2] != (byte)'\r' && line[line.Length - 1] != (byte)'\n')){
                byte[] newLine = new byte[line.Length + 2];
                Array.Copy(line,newLine,line.Length);
                newLine[newLine.Length - 2] = (byte)'\r';
                newLine[newLine.Length - 1] = (byte)'\n';

                line = newLine;
            }

            if(m_SSL){
                m_pSslStream.Write(line);
            }
            else{
                m_pSocketStream.Write(line,0,line.Length);
            }

            m_WrittenCount += line.Length;
            m_LastActivityDate = DateTime.Now;

            // Logging stuff
		    if(m_pLogger != null){
			    if(line.Length < 200){
					m_pLogger.AddSendEntry(m_pEncoding.GetString(line),line.Length);
		        }
			    else{
			        m_pLogger.AddSendEntry("Big binary line, sent " + line.Length.ToString() + " bytes.",line.Length);
		        }
            }
        }

        #endregion

        #region method WritePeriodTerminated

        /// <summary>
        /// Writes period terminated string to socket. The data is terminated by a line containing only a period, that is,
        /// the character sequence "&lt;CRLF&gt;.&lt;CRLF&gt;". Before sending a line of text, check the first
        /// character of the line.If it is a period, one additional period is inserted at the beginning of the line.
        /// </summary>
        /// <param name="data">String data to write.</param>
        public void WritePeriodTerminated(string data)
        {
            WritePeriodTerminated(new MemoryStream(m_pEncoding.GetBytes(data)));
        }

        /// <summary>
        /// Writes period terminated data to socket. The data is terminated by a line containing only a period, that is,
        /// the character sequence "&lt;CRLF&gt;.&lt;CRLF&gt;". Before sending a line of text, check the first
        /// character of the line.If it is a period, one additional period is inserted at the beginning of the line.
        /// </summary>
        /// <param name="stream">Stream which data to write. Reading begins from stream current position and is readed to EOS.</param>
        public void WritePeriodTerminated(Stream stream)
        {
            /* Before sending a line of text, check the first character of the line.
               If it is a period, one additional period is inserted at the beginning of the line.
            */

            int    countSent        = 0;
            byte[] buffer           = new byte[4000];
            int    positionInBuffer = 0;
            bool   CRLF             = false;
            int    lastByte         = -1;
            int    currentByte      = stream.ReadByte();
            while(currentByte > -1){
                // We have CRLF, mark it up
                if(lastByte == '\r' && currentByte == '\n'){
                    CRLF = true;
                }
                // There is CRLF + current byte
                else if(CRLF){
                    // If it is a period, one additional period is inserted at the beginning of the line.
                    if(currentByte == '.'){
                        buffer[positionInBuffer] = (byte)'.';
                        positionInBuffer++;
                    }

                    // CRLF handled, reset it
                    CRLF = false;                   
                }

                buffer[positionInBuffer] = (byte)currentByte;
                positionInBuffer++;
                                       
                lastByte = currentByte;
                
                // Buffer is filled up, write buffer to socket.
                if(positionInBuffer > (4000 - 10)){
                    if(m_SSL){
                        m_pSslStream.Write(buffer,0,positionInBuffer);
                    }
                    else{
                        m_pSocketStream.Write(buffer,0,positionInBuffer);
                    }                    
                    countSent += positionInBuffer;
                    m_WrittenCount += positionInBuffer;
                    m_LastActivityDate = DateTime.Now;
                    positionInBuffer = 0;                    
                }

                currentByte = stream.ReadByte();
            }

            // We have readed all data, write budder data + .<CRLF> or <CRLF>.<CRLF> if data not <CRLF> terminated.
            if(!CRLF){
                buffer[positionInBuffer] = (byte)'\r';
                positionInBuffer++;
                buffer[positionInBuffer] = (byte)'\n';
                positionInBuffer++;
            }

            buffer[positionInBuffer] = (byte)'.';
            positionInBuffer++;
            buffer[positionInBuffer] = (byte)'\r';
            positionInBuffer++;
            buffer[positionInBuffer] = (byte)'\n';
            positionInBuffer++;
       
            if(m_SSL){
                m_pSslStream.Write(buffer,0,positionInBuffer);
            }
            else{
                m_pSocketStream.Write(buffer,0,positionInBuffer);
            }
            countSent += positionInBuffer;
            m_WrittenCount += positionInBuffer;
            m_LastActivityDate = DateTime.Now;
            //-------------------------------------------------------------------------------------//

            // Logging stuff
		    if(m_pLogger != null){
			    if(countSent < 200){
					m_pLogger.AddSendEntry(m_pEncoding.GetString(buffer),buffer.Length);
		        }
			    else{
			        m_pLogger.AddSendEntry("Binary data, sent " + countSent.ToString() + " bytes.",countSent);
		        }
            }
        }

        #endregion


        #region method BeginReadLine

        /// <summary>
        /// Begins reading line from socket asynchrounously.
        /// If maximum allowed line length is exceeded line is read to end, but isn't stored to buffer and exception
        /// is thrown after line reading.
        /// </summary>
        /// <param name="stream">Stream where to store readed line.</param>
        /// <param name="maxLineLength">Maximum line length in bytes.</param>
        /// <param name="tag">User data.</param>
        /// <param name="callback">The method to be called when the asynchronous line read operation is completed.</param>
        public void BeginReadLine(Stream stream,int maxLineLength,object tag,SocketCallBack callback)
        {
            TryToReadLine(callback,tag,stream,maxLineLength,-1,0);
        }

        #region private method TryToReadLine

        /// <summary>
        /// Tries to read line from socket data buffer. If buffer doesn't contain line, 
        /// next buffer data block is getted asynchronously and this method is called again.
        /// </summary>
        /// <param name="callback">The method to be called when the asynchronous line read operation is completed.</param>
        /// <param name="tag">User data.</param>
        /// <param name="stream">Stream where to store readed data.</param>
        /// <param name="maxLineLength">Specifies maximum line legth.</param>
        /// <param name="lastByte">Last byte what was readed pevious method call or -1 if first method call.</param>
        /// <param name="readedCount">Specifies count of bytes readed.</param>
        private void TryToReadLine(SocketCallBack callback,object tag,Stream stream,int maxLineLength,int lastByte,int readedCount)
        {
            // There is no data in buffer, buffer next block asynchronously.
            if(m_AvailableInBuffer == 0){
                BeginBufferDataBlock(this.OnBeginReadLineBufferingCompleted,new object[]{callback,tag,stream,maxLineLength,lastByte,readedCount});
                return;
            }

            // Delay last byte writing, this is because CR, if next is LF, then skip CRLF and terminate reading.

            // This is first method call, buffer 1 byte
            if(lastByte == -1){
                lastByte = ReadByte();
                readedCount++;

                // We use last byte, buffer next block asynchronously.
                if(m_AvailableInBuffer == 0){
                    BeginBufferDataBlock(this.OnBeginReadLineBufferingCompleted,new object[]{callback,tag,stream,maxLineLength,lastByte,readedCount});
                    return;
                }
            }

            int currentByte = ReadByte();
            readedCount++;
            while(currentByte > -1){
                // We got line
                if(lastByte == (byte)'\r' && currentByte == (byte)'\n'){
                    // Logging stuff
				    if(m_pLogger != null){
					    if(stream.CanSeek && stream.Length < 200){
                            byte[] readedData = new byte[stream.Length];
                            stream.Position = 0;
                            stream.Read(readedData,0,readedData.Length);
						    m_pLogger.AddReadEntry(m_pEncoding.GetString(readedData),readedCount);
					    }
					    else{
						    m_pLogger.AddReadEntry("Big binary line, readed " + readedCount.ToString() + " bytes.",readedCount);
					    }
                    }

                    // Maximum allowed length exceeded
                    if(readedCount > maxLineLength){ 
                        if(callback != null){
                            callback(SocketCallBackResult.LengthExceeded,0,new ReadException(ReadReplyCode.LengthExceeded,"Maximum allowed data length exceeded !"),tag);
                        }
                    }

                    // Line readed ok, call callback.
                    if(callback != null){
                        callback(SocketCallBackResult.Ok,readedCount,null,tag);
                    }

                    return;
                }
                else{
                    // Maximum allowed length exceeded, just don't store data.
                    if(readedCount < maxLineLength){
                        stream.WriteByte((byte)lastByte);
                    }                    
                }

                // Read next byte
                lastByte = currentByte;
                if(m_AvailableInBuffer > 0){
                    currentByte = ReadByte();
                    readedCount++;
                }
                // We have use all data in the buffer, buffer next block asynchronously.
                else{
                    BeginBufferDataBlock(this.OnBeginReadLineBufferingCompleted,new object[]{callback,tag,stream,maxLineLength,lastByte,readedCount});
                    return;
                }
            }

            // We should not reach there, if so then socket closed
            // Logging stuff
		    if(m_pLogger != null){
                m_pLogger.AddTextEntry("Remote host closed socket !");
            }
            if(callback != null){                
                callback(SocketCallBackResult.SocketClosed,0,new ReadException(ReadReplyCode.SocketClosed,"Connected host closed socket, read line terminated unexpectedly !"),tag);
            }
        }

        #endregion

        #region private method OnBeginReadLineBufferingCompleted

        /// <summary>
        /// This method is called after asynchronous data buffering is completed.
        /// </summary>
        /// <param name="x">Exception what happened on method execution or null, if operation completed sucessfully.</param>
        /// <param name="tag">User data.</param>
        private void OnBeginReadLineBufferingCompleted(Exception x,object tag)
        {
            object[]       param         = (object[])tag;
            SocketCallBack callback      = (SocketCallBack)param[0];
            object         callbackTag   = (object)param[1];
            Stream         stream        = (Stream)param[2];
            int            maxLineLength = (int)param[3];
            int            lastByte      = (int)param[4];
            int            readedCount   = (int)param[5];

            if(x == null){
                // We didn't get data, this can only happen if socket closed.
                if(m_AvailableInBuffer == 0){
                    // Logging stuff
                    if(m_pLogger != null){
                        m_pLogger.AddTextEntry("Remote host closed socket !");
                    }

                    callback(SocketCallBackResult.SocketClosed,0,null,callbackTag);
                }
                else{
                    TryToReadLine(callback,callbackTag,stream,maxLineLength,lastByte,readedCount);
                }
            }
            else{
                callback(SocketCallBackResult.Exception,0,x,callbackTag);
            }
        }

        #endregion

        #endregion

        #region method BeginReadSpecifiedLength

        /// <summary>
        /// Begins reading specified amount of data from socket asynchronously.
        /// </summary>
        /// <param name="stream">Stream where to store readed data.</param>
        /// <param name="lengthToRead">Specifies number of bytes to read from socket.</param>
        /// <param name="tag">User data.</param>
        /// <param name="callback">The method to be called when the asynchronous read operation is completed.</param>
        public void BeginReadSpecifiedLength(Stream stream,int lengthToRead,object tag,SocketCallBack callback)
        {
            TryToReadReadSpecifiedLength(stream,lengthToRead,tag,callback,0);
        }

        #region method TryToReadReadSpecifiedLength

        /// <summary>
        /// Tries to read specified length of data from socket data buffer. If buffer doesn't contain data, 
        /// next buffer data block is getted asynchronously and this method is called again.
        /// </summary>
        /// <param name="stream">Stream where to store readed data.</param>
        /// <param name="lengthToRead">Specifies number of bytes to read from socket.</param>
        /// <param name="tag">User data.</param>
        /// <param name="callback">The method to be called when the asynchronous read operation is completed.</param>
        /// <param name="readedCount">Specifies count of bytes readed.</param>
        private void TryToReadReadSpecifiedLength(Stream stream,int lengthToRead,object tag,SocketCallBack callback,int readedCount)
        {
            // There is no data in buffer, buffer next block asynchronously.
            if(m_AvailableInBuffer == 0){
                BeginBufferDataBlock(this.OnBeginReadSpecifiedLengthBufferingCompleted,new object[]{callback,tag,stream,lengthToRead,readedCount});
                return;
            }
                
            // Buffer has less data than that we need
            int lengthLeftForReading = lengthToRead - readedCount;
            if(lengthLeftForReading > m_AvailableInBuffer){
                stream.Write(m_Buffer,m_OffsetInBuffer,m_AvailableInBuffer);
                stream.Flush();

                readedCount += m_AvailableInBuffer;
                // We used buffer directly, sync buffer info !!!
                m_OffsetInBuffer = 0;
                m_AvailableInBuffer = 0;

                BeginBufferDataBlock(this.OnBeginReadSpecifiedLengthBufferingCompleted,new object[]{callback,tag,stream,lengthToRead,readedCount});
            }
            // Buffer contains all data we need
            else{
                stream.Write(m_Buffer,m_OffsetInBuffer,lengthLeftForReading);
                stream.Flush();

                readedCount += lengthLeftForReading;
                // We used buffer directly, sync buffer info !!!
                m_OffsetInBuffer += lengthLeftForReading;
                m_AvailableInBuffer -= lengthLeftForReading;

                // Logging stuff
				if(m_pLogger != null){
				    if(stream.CanSeek && stream.Length < 200){
                        byte[] readedData = new byte[stream.Length];
                        stream.Position = 0;
                        stream.Read(readedData,0,readedData.Length);
						m_pLogger.AddReadEntry(m_pEncoding.GetString(readedData),lengthToRead);
					}
					else{
					    m_pLogger.AddReadEntry("Big binary data, readed " + readedCount + " bytes.",readedCount);
					}
                }

                // Data readed ok, call callback.
                if(callback != null){
                    callback(SocketCallBackResult.Ok,readedCount,null,tag);
                }
            }
        }

        #endregion

        #region private method OnBeginReadSpecifiedLengthBufferingCompleted

        /// <summary>
        /// This method is called after asynchronous data buffering is completed.
        /// </summary>
        /// <param name="x">Exception what happened on method execution or null, if operation completed sucessfully.</param>
        /// <param name="tag">User data.</param>
        private void OnBeginReadSpecifiedLengthBufferingCompleted(Exception x,object tag)
        {
            object[]       param         = (object[])tag;
            SocketCallBack callback      = (SocketCallBack)param[0];
            object         callbackTag   = (object)param[1];
            Stream         stream        = (Stream)param[2];
            int            lengthToRead  = (int)param[3];
            int            readedCount   = (int)param[4];

            if(x == null){
                // We didn't get data, this can only happen if socket closed.
                if(m_AvailableInBuffer == 0){
                    // Logging stuff
                    if(m_pLogger != null){
                        m_pLogger.AddTextEntry("Remote host closed socket !");
                    }

                    callback(SocketCallBackResult.SocketClosed,0,null,callbackTag);
                }
                else{
                    TryToReadReadSpecifiedLength(stream,lengthToRead,callbackTag,callback,readedCount);
                }
            }
            else{
                callback(SocketCallBackResult.Exception,0,x,callbackTag);
            }
        }

        #endregion

        #endregion

        #region method BeginReadPeriodTerminated

        /// <summary>
        /// Begins reading period terminated data. The data is terminated by a line containing only a period, that is,
        /// the character sequence "&lt;CRLF&gt;.&lt;CRLF&gt;".
        /// When a line of text is received, it checks the line. If the line is composed of a single period,
        /// it is treated as the end of data indicator.  If the first character is a period and there are 
        /// other characters on the line, the first character is deleted.
        /// If maximum allowed data length is exceeded data is read to end, but isn't stored to stream and exception
        /// is thrown after data reading.
        /// </summary>
        /// <param name="stream">Stream where to store readed data.</param>
        /// <param name="maxLength">Maximum data length in bytes.</param>
        /// <param name="tag">User data.</param>
        /// <param name="callback">The method to be called when the asynchronous read operation is completed.</param>
        public void BeginReadPeriodTerminated(Stream stream,int maxLength,object tag,SocketCallBack callback)
        {
            TryToReadPeriodTerminated(callback,tag,stream,maxLength,-1,0,false,false);
        }

        #region method TryToReadPeriodTerminated

        /// <summary>
        /// Tries to read period terminated data from socket data buffer. If buffer doesn't contain 
        /// period terminated data,next buffer data block is getted asynchronously and this method is called again.
        /// </summary>
        /// <param name="callback">The method to be called when the asynchronous period terminated read operation is completed.</param>
        /// <param name="tag">User data.</param>
        /// <param name="stream">Stream where to store readed data.</param>
        /// <param name="maxLength">Specifies maximum data legth in bytes.</param>
        /// <param name="readedCount">Specifies count of bytes readed.</param>
        /// <param name="lastByte">Last byte what was readed pevious method call or -1 if first method call.</param>
        /// <param name="lineBreak">Specifies if there is active line break.</param>
        /// <param name="expectCRLF">Specifies if terminating CRLF is expected.</param>
        private void TryToReadPeriodTerminated(SocketCallBack callback,object tag,Stream stream,int maxLength,int lastByte,int readedCount,bool lineBreak,bool expectCRLF)
        {
            /* When a line of text is received by the server, it checks the line.
               If the line is composed of a single period, it is treated as the 
               end of data indicator.  If the first character is a period and 
               there are other characters on the line, the first character is deleted.
            */

            // There is no data in buffer, buffer next block asynchronously.
            if(m_AvailableInBuffer == 0){
                BeginBufferDataBlock(this.OnBeginReadPeriodTerminatedBufferingCompleted,new object[]{callback,tag,stream,maxLength,lastByte,readedCount,lineBreak,expectCRLF});
                return;
            }

            // Delay last byte writing, this is because CR, if next is LF, then last byte isn't written.
            
            // This is first method call, buffer 1 byte
            if(lastByte == -1){
                lastByte = ReadByte();
                readedCount++;

                // We used last byte, buffer next block asynchronously.
                if(m_AvailableInBuffer == 0){
                    BeginBufferDataBlock(this.OnBeginReadPeriodTerminatedBufferingCompleted,new object[]{callback,tag,stream,maxLength,lastByte,readedCount,lineBreak,expectCRLF});
                    return;
                }
            }

            byte[] buffer           = new byte[8000];
            int    positionInBuffer = 0;

            int currentByte = ReadByte();
            readedCount++;
            while(currentByte > -1){                                
                // We got <CRLF> + 1 char, we must skip that char if it is '.'.
                if(lineBreak){
                    lineBreak = false;

                    // We must skip this char if it is '.'
                    if(currentByte == '.'){
                        expectCRLF = true;
                        
                        // Read next byte
                        if(m_AvailableInBuffer > 0){
                            currentByte = ReadByte();
                            readedCount++;
                        }
                        // We have use all data in the buffer, buffer next block asynchronously.
                        else{
                            // There is data in buffer, flush it
                            if(positionInBuffer > 0){
                                stream.Write(buffer,0,positionInBuffer);
                                positionInBuffer = 0;
                            }

                            BeginBufferDataBlock(this.OnBeginReadPeriodTerminatedBufferingCompleted,new object[]{callback,tag,stream,maxLength,lastByte,readedCount,lineBreak,expectCRLF});
                            return;
                        }
                    }                    
                }
                // We got <CRLF>
                else if(lastByte == (byte)'\r' && currentByte == (byte)'\n'){
                    lineBreak = true;

                    // We have <CRLF>.<CRLF>, skip last <CRLF>.
                    if(expectCRLF){
                        // There is data in buffer, flush it
                        if(positionInBuffer > 0){
                            stream.Write(buffer,0,positionInBuffer);
                            positionInBuffer = 0;
                        }

                        // Logging stuff
				        if(m_pLogger != null){
					        if(stream.CanSeek && stream.Length < 200){
                                byte[] readedData = new byte[stream.Length];
                                stream.Position = 0;
                                stream.Read(readedData,0,readedData.Length);
						        m_pLogger.AddReadEntry(m_pEncoding.GetString(readedData),readedCount);
					        }
					        else{
						        m_pLogger.AddReadEntry("Big binary data, readed " + readedCount.ToString() + " bytes.",readedCount);
					        }
                        }

                        // Maximum allowed length exceeded
                        if(readedCount > maxLength){
                            if(callback != null){
                                callback(SocketCallBackResult.LengthExceeded,0,new ReadException(ReadReplyCode.LengthExceeded,"Maximum allowed data length exceeded !"),tag);                                
                            }
                            return;
                        }

                        // Data readed ok, call callback.
                        if(callback != null){
                            callback(SocketCallBackResult.Ok,readedCount,null,tag);
                        }
                        return;
                    }
                }

                // current char isn't CRLF part, so it isn't <CRLF>.<CRLF> terminator.
                if(expectCRLF && !(currentByte == (byte)'\r' || currentByte == (byte)'\n')){
                    expectCRLF = false;
                }

                // Maximum allowed length exceeded, just don't store data.
                if(readedCount < maxLength){
                    // Buffer is filled up, write buffer to stream
                    if(positionInBuffer > (buffer.Length - 2)){
                        stream.Write(buffer,0,positionInBuffer);
                        positionInBuffer = 0;
                    }

                    buffer[positionInBuffer] = (byte)lastByte;
                    positionInBuffer++;
                }
                    
                // Read next byte
                lastByte = currentByte;
                if(m_AvailableInBuffer > 0){
                    currentByte = ReadByte();
                    readedCount++;
                }
                // We have use all data in the buffer, buffer next block asynchronously.
                else{
                    // There is data in buffer, flush it
                    if(positionInBuffer > 0){
                        stream.Write(buffer,0,positionInBuffer);
                        positionInBuffer = 0;
                    }

                    BeginBufferDataBlock(this.OnBeginReadPeriodTerminatedBufferingCompleted,new object[]{callback,tag,stream,maxLength,lastByte,readedCount,lineBreak,expectCRLF});
                    return;
                }                
            }

            // We should never reach here.
            if(callback != null){
                callback(SocketCallBackResult.Exception,0,new Exception("Never should reach there ! method TryToReadPeriodTerminated out of while loop."),tag);
            }
        }

        #endregion

        #region private method OnBeginReadPeriodTerminatedBufferingCompleted

        /// <summary>
        /// This method is called after asynchronous data buffering is completed.
        /// </summary>
        /// <param name="x">Exception what happened on method execution or null, if operation completed sucessfully.</param>
        /// <param name="tag">User data.</param>
        private void OnBeginReadPeriodTerminatedBufferingCompleted(Exception x,object tag)
        {
            object[]       param         = (object[])tag;
            SocketCallBack callback      = (SocketCallBack)param[0];
            object         callbackTag   = (object)param[1];
            Stream         stream        = (Stream)param[2];
            int            maxLength     = (int)param[3];
            int            lastByte      = (int)param[4];
            int            readedCount   = (int)param[5];
            bool           lineBreak     = (bool)param[6];
            bool           expectCRLF    = (bool)param[7];

            if(x == null){
                // We didn't get data, this can only happen if socket closed.
                if(m_AvailableInBuffer == 0){
                    // Logging stuff
                    if(m_pLogger != null){
                        m_pLogger.AddTextEntry("Remote host closed socket !");
                    }

                    callback(SocketCallBackResult.SocketClosed,0,null,callbackTag);
                }
                else{
                    TryToReadPeriodTerminated(callback,callbackTag,stream,maxLength,lastByte,readedCount,lineBreak,expectCRLF);
                }
            }
            else{
                callback(SocketCallBackResult.Exception,0,x,callbackTag);
            }
        }

        #endregion

        #endregion


        #region method BeginWrite

        /// <summary>
        /// Begins writing specified data to socket.
        /// </summary>
        /// <param name="stream">Stream which data to write to socket. Reading starts from stream current position and will be readed to EOS.</param>
        /// <param name="tag">User data.</param>
        /// <param name="callback">The method to be called when the asynchronous write operation is completed.</param>
        public void BeginWrite(Stream stream,object tag,SocketCallBack callback)
        {
            // Allow socket to optimise sends
            m_pSocket.NoDelay = false;

            BeginProcessingWrite(stream,tag,callback,0);
        }

        #region method BeginProcessingWrite

        /// <summary>
        /// Starts sending data block to socket.
        /// </summary>
        /// <param name="stream">Stream which data to write.</param>
        /// <param name="tag">User data.</param>
        /// <param name="callback">The method to be called when the asynchronous write operation is completed</param>
        /// <param name="countSent">Specifies how many data is sent.</param>
        private void BeginProcessingWrite(Stream stream,object tag,SocketCallBack callback,int countSent)
        {
            byte[] buffer = new byte[4000];
            int readedCount = stream.Read(buffer,0,buffer.Length);
            // There data to send
            if(readedCount > 0){
                countSent += readedCount;
                m_WrittenCount += readedCount;

                if(m_SSL){
                    m_pSslStream.BeginWrite(buffer,0,readedCount,new AsyncCallback(this.OnBeginWriteCallback),new object[]{stream,tag,callback,countSent});
                }
                else{
                    m_pSocketStream.BeginWrite(buffer,0,readedCount,new AsyncCallback(this.OnBeginWriteCallback),new object[]{stream,tag,callback,countSent});
                }               
            }
            // We have sent all data
            else{
                // Logging stuff
		        if(m_pLogger != null){
			        if(stream.CanSeek && stream.Length < 200){
                        byte[] sentData = new byte[stream.Length];
                        stream.Position = 0;
                        stream.Read(sentData,0,sentData.Length);
						m_pLogger.AddSendEntry(m_pEncoding.GetString(sentData),countSent);
					}
			        else{
			            m_pLogger.AddSendEntry("Big binary data, sent " + countSent.ToString() + " bytes.",countSent);
		            }
                }

                // Line sent ok, call callback.
                if(callback != null){
				    callback(SocketCallBackResult.Ok,countSent,null,tag);
			    }
            }
        }

        #endregion

        #region method OnBeginWriteCallback

        /// <summary>
        /// This method is called after asynchronous datablock send is completed.
        /// </summary>
        /// <param name="ar"></param>
        private void OnBeginWriteCallback(IAsyncResult ar)
        {
            object[]       param     = (object[])ar.AsyncState;            
            Stream         stream    = (Stream)param[0];
            object         tag       = param[1];
            SocketCallBack callBack  = (SocketCallBack)param[2];
            int            countSent = (int)param[3];

            try{
                if(m_SSL){
                    m_pSslStream.EndWrite(ar);
                }
                else{
                    m_pSocketStream.EndWrite(ar);
                }
                
                m_LastActivityDate = DateTime.Now;

                BeginProcessingWrite(stream,tag,callBack,countSent);
            }
            catch(Exception x){
                if(callBack != null){
					callBack(SocketCallBackResult.Exception,0,x,tag);
				}
            }
        }

        #endregion

        #endregion

        #region method BeginWriteLine

        /// <summary>
        /// Begins specified line sending to socket asynchronously.
        /// </summary>
        /// <param name="line">Line to send.</param>
        /// <param name="callback">The method to be called when the asynchronous line write operation is completed.</param>
        public void BeginWriteLine(string line,SocketCallBack callback)
        {
            // Don't allow to wait after we send data, because there won't no more data
            m_pSocket.NoDelay = true;

            BeginWriteLine(line,null,callback);
        }

        /// <summary>
        /// Begins specified line sending to socket asynchronously.
        /// </summary>
        /// <param name="line">Line to send.</param>
        /// <param name="tag">User data.</param>
        /// <param name="callback">The method to be called when the asynchronous line write operation is completed.</param>
        public void BeginWriteLine(string line,object tag,SocketCallBack callback)
        {
            // Don't allow to wait after we send data, because there won't no more data
            m_pSocket.NoDelay = true;

            if(!line.EndsWith("\r\n")){
                line += "\r\n";
            }

            byte[] lineBytes = m_pEncoding.GetBytes(line);
            if(m_SSL){
                m_pSslStream.BeginWrite(lineBytes,0,lineBytes.Length,this.OnBeginWriteLineCallback,new object[]{tag,callback,lineBytes});
            }
            else{
                m_pSocketStream.BeginWrite(lineBytes,0,lineBytes.Length,this.OnBeginWriteLineCallback,new object[]{tag,callback,lineBytes});
            }
        }

        #region method OnBeginWriteLineCallback

        /// <summary>
        /// This method is called after asynchronous WriteLine is completed.
        /// </summary>
        /// <param name="ar"></param>
        private void OnBeginWriteLineCallback(IAsyncResult ar)
        {
            object[]       param     = (object[])ar.AsyncState;
            object         tag       = param[0];
            SocketCallBack callBack  = (SocketCallBack)param[1];
            byte[]         lineBytes = (byte[])param[2];

            try{
                if(m_SSL){
                    m_pSslStream.EndWrite(ar);
                }
                else{
                    m_pSocketStream.EndWrite(ar);
                }

                m_WrittenCount += lineBytes.Length;
                m_LastActivityDate = DateTime.Now;

                // Logging stuff
		        if(m_pLogger != null){
			        if(lineBytes.Length < 200){
					    m_pLogger.AddSendEntry(m_pEncoding.GetString(lineBytes),lineBytes.Length);
		            }
			        else{
			            m_pLogger.AddSendEntry("Big binary line, sent " + lineBytes.Length.ToString() + " bytes.",lineBytes.Length);
		            }
                }

                // Line sent ok, call callback.
                if(callBack != null){
				    callBack(SocketCallBackResult.Ok,lineBytes.Length,null,tag);
			    }
            }
            catch(Exception x){
                if(callBack != null){
					callBack(SocketCallBackResult.Exception,0,x,tag);
				}
            }
        }

        #endregion

        #endregion

        #region method BeginWritePeriodTerminated

        #region struct _BeginWritePeriodTerminated_State

        /// <summary>
        /// BeginWritePeriodTerminated state obejct.
        /// </summary>
        private struct _BeginWritePeriodTerminated_State
        {            
            private Stream         m_Stream;
            private bool           m_CloseStream;
            private object         m_Tag;
            private SocketCallBack m_Callback;
            private bool           m_HasCRLF;
            private int            m_LastByte;
            private int            m_CountSent;

            /// <summary>
            /// Default constructor.
            /// </summary>
            /// <param name="stream">Source stream.</param>
            /// <param name="closeStream">Specifies if stream must be closed after reading is completed.</param>
            /// <param name="tag">User data.</param>
            /// <param name="callback">Callback what to call if asynchronous data writing completes.</param>
            public _BeginWritePeriodTerminated_State(Stream stream,bool closeStream,object tag,SocketCallBack callback)
            {
                m_Stream      = stream;
                m_CloseStream = closeStream;
                m_Tag         = tag;
                m_Callback    = callback;
                m_HasCRLF     = false;
                m_LastByte    = -1;
                m_CountSent   = 0;
            }


            #region Properties Implementation

            /// <summary>
            /// Gets source stream.
            /// </summary>
            public Stream Stream
            {
                get{ return m_Stream; }
            }

            /// <summary>
            /// Gets if stream must be closed if reading completed.
            /// </summary>
            public bool CloseStream
            {
                get{ return m_CloseStream; }
            }

            /// <summary>
            /// Gets user data.
            /// </summary>
            public object Tag
            {
                get{ return m_Tag; }
            }

            /// <summary>
            /// Gets callback what must be called if asynchronous write ends.
            /// </summary>
            public SocketCallBack Callback
            {
                get{ return m_Callback; }
            }

            /// <summary>
            /// Gets or sets if last sent data ends with CRLF.
            /// </summary>
            public bool HasCRLF
            {
                get{ return m_HasCRLF; }

                set{ m_HasCRLF = value; }
            }

            /// <summary>
            /// Gets or sets what is last sent byte.
            /// </summary>
            public int LastByte
            {
                get{ return m_LastByte; }

                set{ m_LastByte = value; }
            }

            /// <summary>
            /// Gets or sets how many bytes has written to socket.
            /// </summary>
            public int CountSent
            {
                get{ return m_CountSent; }

                set{ m_CountSent = value; }
            }

            #endregion

        }

        #endregion

        /// <summary>
        /// Begins writing period terminated data to socket. The data is terminated by a line containing only a period, that is,
        /// the character sequence "&lt;CRLF&gt;.&lt;CRLF&gt;". Before sending a line of text, check the first
        /// character of the line.If it is a period, one additional period is inserted at the beginning of the line.
        /// </summary>
        /// <param name="stream">Stream which data to write. Reading begins from stream current position and is readed to EOS.</param>
        /// <param name="tag">User data.</param>
        /// <param name="callback">The method to be called when the asynchronous write operation is completed.</param>
        public void BeginWritePeriodTerminated(Stream stream,object tag,SocketCallBack callback)
        {           
            BeginWritePeriodTerminated(stream,false,tag,callback);
        }
        
        /// <summary>
        /// Begins writing period terminated data to socket. The data is terminated by a line containing only a period, that is,
        /// the character sequence "&lt;CRLF&gt;.&lt;CRLF&gt;". Before sending a line of text, check the first
        /// character of the line.If it is a period, one additional period is inserted at the beginning of the line.
        /// </summary>
        /// <param name="stream">Stream which data to write. Reading begins from stream current position and is readed to EOS.</param>
        /// <param name="closeStream">Specifies if stream is closed after write operation has completed.</param>
        /// <param name="tag">User data.</param>
        /// <param name="callback">The method to be called when the asynchronous write operation is completed.</param>
        public void BeginWritePeriodTerminated(Stream stream,bool closeStream,object tag,SocketCallBack callback)
        {
            // Allow socket to optimise sends
            m_pSocket.NoDelay = false;
                        
            _BeginWritePeriodTerminated_State state = new _BeginWritePeriodTerminated_State(stream,closeStream,tag,callback);
            BeginProcessingWritePeriodTerminated(state);
        }

        #region method BeginProcessingWritePeriodTerminated

        /// <summary>
        /// Reads data block from state.Stream and begins writing it to socket.
        /// This method is looped while all data has been readed from state.Stream, then sate.Callback is called.
        /// </summary>
        /// <param name="state">State info.</param>        
        private void BeginProcessingWritePeriodTerminated(_BeginWritePeriodTerminated_State state)
        {                              
            /* Before sending a line of text, check the first character of the line.
               If it is a period, one additional period is inserted at the beginning of the line.
            */
            
            byte[] buffer           = new byte[4000];
            int    positionInBuffer = 0;
            int    currentByte      = state.Stream.ReadByte();
            while(currentByte > -1){
                // We have CRLF, mark it up
                if(state.LastByte == '\r' && currentByte == '\n'){
                    state.HasCRLF = true;
                }
                // There is CRLF + current byte
                else if(state.HasCRLF){
                    // If it is a period, one additional period is inserted at the beginning of the line.
                    if(currentByte == '.'){
                        buffer[positionInBuffer] = (byte)'.';
                        positionInBuffer++;
                    }

                    // CRLF handled, reset it
                    state.HasCRLF = false;                   
                }

                buffer[positionInBuffer] = (byte)currentByte;
                positionInBuffer++;
                                       
                state.LastByte = currentByte;
                
                // Buffer is filled up, begin writing buffer to socket.
                if(positionInBuffer > (4000 - 10)){
                    state.CountSent += positionInBuffer;
                    m_WrittenCount += positionInBuffer;
                                        
                    if(m_SSL){
                        m_pSslStream.BeginWrite(buffer,0,positionInBuffer,this.OnBeginWritePeriodTerminatedCallback,state);
                    }
                    else{
                        m_pSocketStream.BeginWrite(buffer,0,positionInBuffer,this.OnBeginWritePeriodTerminatedCallback,state);
                    }
                    return;
                }   
             
                currentByte = state.Stream.ReadByte();
            }

            // We have readed all data, write .<CRLF> or <CRLF>.<CRLF> if data not <CRLF> terminated.
            if(!state.HasCRLF){
                buffer[positionInBuffer] = (byte)'\r';
                positionInBuffer++;
                buffer[positionInBuffer] = (byte)'\n';
                positionInBuffer++;
            }

            buffer[positionInBuffer] = (byte)'.';
            positionInBuffer++;
            buffer[positionInBuffer] = (byte)'\r';
            positionInBuffer++;
            buffer[positionInBuffer] = (byte)'\n';
            positionInBuffer++;
       
            if(m_SSL){
                m_pSslStream.Write(buffer,0,positionInBuffer);
            }
            else{
                m_pSocketStream.Write(buffer,0,positionInBuffer);
            }
            state.CountSent += positionInBuffer;
            m_WrittenCount += positionInBuffer;
            m_LastActivityDate = DateTime.Now;
            //-------------------------------------------------------------------------------------//

            // Logging stuff
		    if(m_pLogger != null){
			    if(state.CountSent < 200){
					m_pLogger.AddSendEntry(m_pEncoding.GetString(buffer),buffer.Length);
		        }
			    else{
			        m_pLogger.AddSendEntry("Binary data, sent " + state.CountSent.ToString() + " bytes.",state.CountSent);
		        }
            }

            // We don't need stream any more, close it
            if(state.CloseStream){
                try{
                    state.Stream.Close();
                }
                catch{
                }
            }

            // Data sent ok, call callback.
            if(state.Callback != null){
			    state.Callback(SocketCallBackResult.Ok,state.CountSent,null,state.Tag);
		    }            
        }

        #endregion

        #region method OnBeginWritePeriodTerminatedCallback

        /// <summary>
        /// This method is called after asynchronous datablock send is completed.
        /// </summary>
        /// <param name="ar"></param>
        private void OnBeginWritePeriodTerminatedCallback(IAsyncResult ar)
        {
            _BeginWritePeriodTerminated_State state = (_BeginWritePeriodTerminated_State)ar.AsyncState;

            try{
                if(m_SSL){
                    m_pSslStream.EndWrite(ar);
                }
                else{
                    m_pSocketStream.EndWrite(ar);
                }

                m_LastActivityDate = DateTime.Now;

                BeginProcessingWritePeriodTerminated(state);
            }
            catch(Exception x){
                // We don't need stream any more, close it
                if(state.CloseStream){
                    try{
                        state.Stream.Close();
                    }
                    catch{
                    }
                }

                if(state.Callback != null){
					state.Callback(SocketCallBackResult.Exception,0,x,state.Tag);
				}
            }
        }

        #endregion

        #endregion


        #region method SendTo

        /// <summary>
        /// Sends data to the specified end point.
        /// </summary>
        /// <param name="data">Data to send.</param>
        /// <param name="remoteEP">Remote endpoint where to send data.</param>
        /// <returns>Returns number of bytes actualy sent.</returns>
        public int SendTo(byte[] data,EndPoint remoteEP)
        {
            return m_pSocket.SendTo(data,remoteEP);
        }

        #endregion



        #region method BufferDataBlock

        /// <summary>
        /// Buffers data from socket if needed. If there is data in buffer, no buffering is done.
        /// </summary>
        private void BufferDataBlock()
        {
            lock(this){
                // There is no data in buffer, buffer next data block
                if(m_AvailableInBuffer == 0){
                    m_OffsetInBuffer = 0;

                    if(m_SSL){
                        m_AvailableInBuffer = m_pSslStream.Read(m_Buffer,0,m_Buffer.Length);
                    }
                    else{
                        m_AvailableInBuffer = m_pSocket.Receive(m_Buffer);
                    }
                    m_ReadedCount += m_AvailableInBuffer;             
                    m_LastActivityDate = DateTime.Now;
                }
            }
        }

        #endregion

        #region method BeginBufferDataBlock

        /// <summary>
        /// Start buffering data from socket asynchronously.
        /// </summary>
        /// <param name="callback">The method to be called when the asynchronous data buffering operation is completed.</param>
        /// <param name="tag">User data.</param>
        private void BeginBufferDataBlock(BufferDataBlockCompleted callback,object tag)
        {
            if(m_AvailableInBuffer == 0){
                m_OffsetInBuffer = 0;
              
                if(m_SSL){
                    m_pSslStream.BeginRead(m_Buffer,0,m_Buffer.Length,this.OnBeginBufferDataBlockCallback,new object[]{callback,tag});
                }
                else{
                    m_pSocket.BeginReceive(m_Buffer,0,m_Buffer.Length,SocketFlags.None,this.OnBeginBufferDataBlockCallback,new object[]{callback,tag});
                }
            }            
        }

        #region method OnBeginBufferDataBlockCallback

        /// <summary>
        /// This method is called after asynchronous BeginBufferDataBlock is completed.
        /// </summary>
        /// <param name="ar"></param>
        private void OnBeginBufferDataBlockCallback(IAsyncResult ar)
        {
            object[] param = (object[])ar.AsyncState;
            BufferDataBlockCompleted callback = (BufferDataBlockCompleted)param[0];
            object tag = param[1];

            try{
                // Socket closed by this.Disconnect() or closed by remote host.
                if(m_pSocket == null || !m_pSocket.Connected){                   
                    m_AvailableInBuffer = 0;
                }
                else{
                    if(m_SSL){
                        m_AvailableInBuffer = m_pSslStream.EndRead(ar);
                    }
                    else{
                        m_AvailableInBuffer = m_pSocket.EndReceive(ar);
                    }
                }
                m_ReadedCount += m_AvailableInBuffer;
                m_LastActivityDate = DateTime.Now;

                if(callback != null){
                    callback(null,tag);
                }
            }
            catch(Exception x){
                if(callback != null){
                    callback(x,tag);
                }
            }
        }

        #endregion

        #endregion



        #region Properites Implementation

        /// <summary>
        /// Gets or sets socket default encoding. 
        /// </summary>
        public Encoding Encoding
        {
            get{ return m_pEncoding; }

            set{
                if(m_pEncoding == null){
                    throw new ArgumentNullException("Encoding");
                }
 
                m_pEncoding = value; 
            }
        }

        /// <summary>
		/// Gets or sets logging source. If this is setted, reads/writes are logged to it.
		/// </summary>
		public SocketLogger Logger
		{
			get{ return m_pLogger; }

			set{ m_pLogger = value; }
		}

        /// <summary>
        /// Gets raw uderlaying socket.
        /// </summary>
        public Socket RawSocket
        {
            get{ return m_pSocket; }
        }

        /// <summary>
        /// Gets if socket is connected.
        /// </summary>
        public bool Connected
        {
            get{ return m_pSocket != null && m_pSocket.Connected; }
        }

        /// <summary>
        /// Gets the local endpoint.
        /// </summary>
        public EndPoint LocalEndPoint
        {
            get{ 
                if(m_pSocket == null){
                    return null;
                }
                else{
                    return m_pSocket.LocalEndPoint; 
                }
            }
        }

        /// <summary>
        /// Gets the remote endpoint.
        /// </summary>
        public EndPoint RemoteEndPoint
        {
            get{ 
                if(m_pSocket == null){
                    return null;
                }
                else{
                    return m_pSocket.RemoteEndPoint; 
                }
            }
        }

        /// <summary>
        /// Gets if socket is connected via SSL.
        /// </summary>
        public bool SSL
        {
            get{ return m_SSL; }
        }

        /// <summary>
        /// Gets how many bytes are readed through this socket.
        /// </summary>
        public long ReadedCount
        {
            get{ return m_ReadedCount; }
        }
        
        /// <summary>
        /// Gets how many bytes are written through this socket.
        /// </summary>
        public long WrittenCount
        {
            get{ return m_WrittenCount; }
        }

        /// <summary>
        /// Gets when was last socket(read or write) activity.
        /// </summary>
        public DateTime LastActivity
        {
            get{ return m_LastActivityDate; }
        }

        #endregion

    }
}
